import pandas as pd
import ping3
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
import concurrent.futures
import openpyxl
from openpyxl.styles import Font, Alignment
import datetime
import schedule
import time
import winsound
from PIL import Image, ImageDraw
import pystray
import os
import json
import ipaddress

class NetworkCheckerApp:
    def __init__(self):
        # 初始化变量
        self.is_checking = False
        self.sound_file = None
        self.max_workers = 50
        self.current_batch = 0
        self.total_batches = 0
        self.executor = None  # 添加线程池管理器

        # 创建主窗口
        self.root = tk.Tk()
        self.root.title("网络连通性检查工具 v1.1")
        self.root.geometry("1000x800")

        # 初始化各项配置
        self.load_settings()
        self.create_widgets()
        self.create_tray_icon()

        # 绑定事件
        self.root.protocol('WM_DELETE_WINDOW', self.on_closing)

    def load_settings(self):
        """加载设置"""
        try:
            if os.path.exists('settings.json'):
                with open('settings.json', 'r') as f:
                    settings = json.load(f)
                    self.max_workers = settings.get('max_workers', 50)
                    self.sound_file = settings.get('sound_file', None)
        except Exception:
            pass

    def save_settings(self):
        """保存设置"""
        try:
            settings = {
                'max_workers': self.max_workers,
                'sound_file': self.sound_file
            }
            with open('settings.json', 'w') as f:
                json.dump(settings, f)
        except Exception:
            pass

    def play_sound(self, event_type):
        """播放声音提示"""
        try:
            if event_type == 'online':
                winsound.Beep(1000, 100)
            elif event_type == 'offline':
                winsound.Beep(500, 100)
            elif event_type == 'complete':
                winsound.Beep(800, 200)
        except Exception:
            pass

    def create_widgets(self):
        """创建界面组件"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 控制面板
        control_frame = ttk.Frame(main_frame)
        control_frame.pack(fill=tk.X, pady=5)

        # 操作按钮
        self.load_btn = ttk.Button(control_frame, text="导入Excel", command=self.load_excel)
        self.load_btn.pack(side=tk.LEFT, padx=5)
        
        self.check_btn = ttk.Button(control_frame, text="开始检测", command=self.start_check)
        self.check_btn.pack(side=tk.LEFT, padx=5)
        
        self.stop_btn = ttk.Button(control_frame, text="停止检测", command=self.stop_check, state=tk.DISABLED)
        self.stop_btn.pack(side=tk.LEFT, padx=5)
        
        self.export_btn = ttk.Button(control_frame, text="导出结果", command=self.export_result)
        self.export_btn.pack(side=tk.LEFT, padx=5)

        # 状态栏
        status_frame = ttk.Frame(main_frame)
        status_frame.pack(fill=tk.X, pady=5)
        
        self.status_label = ttk.Label(status_frame, text="就绪")
        self.status_label.pack(side=tk.LEFT)
        
        self.progress = ttk.Progressbar(status_frame, mode='determinate')
        self.progress.pack(side=tk.RIGHT, fill=tk.X, expand=True)

        # 数据表格
        self.create_treeview(main_frame)

        # IP地址段输入
        ip_frame = ttk.LabelFrame(main_frame, text="IP地址段管理")
        ip_frame.pack(fill=tk.X, pady=5)
        
        self.ip_entry = ttk.Entry(ip_frame, width=40)
        self.ip_entry.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(ip_frame, text="添加", command=self.add_ip_range).pack(side=tk.LEFT)
        ttk.Button(ip_frame, text="清空表格", command=self.clear_treeview).pack(side=tk.RIGHT)

        # 右键菜单
        self.context_menu = tk.Menu(self.root, tearoff=0)
        self.context_menu.add_command(label="重新检测选中项", command=self.recheck_selected)
        self.context_menu.add_command(label="切换选中状态", command=self.toggle_selection)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="删除选中项", command=self.delete_selected)

    def create_treeview(self, parent):
        """创建结果表格"""
        columns = ('selected', 'id', 'ip', 'remark', 'status', 'latency', 'timestamp')
        self.tree = ttk.Treeview(parent, columns=columns, show='headings', selectmode='extended')

        # 设置列属性
        self.tree.heading('selected', text='选择', command=lambda: self.sort_column('selected', False))
        self.tree.heading('id', text='序号', command=lambda: self.sort_column('id', False))
        self.tree.heading('ip', text='IP地址', command=lambda: self.sort_column('ip', False))
        self.tree.heading('remark', text='备注')
        self.tree.heading('status', text='状态')
        self.tree.heading('latency', text='延迟')
        self.tree.heading('timestamp', text='检测时间')

        self.tree.column('selected', width=50, anchor='center')
        self.tree.column('id', width=50, anchor='center')
        self.tree.column('ip', width=150, anchor='center')
        self.tree.column('remark', width=200)
        self.tree.column('status', width=100, anchor='center')
        self.tree.column('latency', width=100, anchor='center')
        self.tree.column('timestamp', width=150, anchor='center')

        # 添加滚动条
        scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 绑定事件
        self.tree.bind("<Button-3>", self.show_context_menu)
        self.tree.bind("<Button-1>", self.handle_click)

    def load_excel(self):
        """导入Excel文件"""
        try:
            file_path = filedialog.askopenfilename(filetypes=[("Excel文件", "*.xlsx;*.xls")])
            if not file_path:
                return

            df = pd.read_excel(file_path)
            ip_col = next((col for col in ['IP', 'IP地址'] if col in df.columns), None)
            remark_col = next((col for col in ['备注', '说明'] if col in df.columns), None)

            if not ip_col:
                raise ValueError("未找到IP地址列")

            self.clear_treeview()
            for idx, row in df.iterrows():
                ip = str(row[ip_col]).strip()
                if not self.is_valid_ip(ip):
                    continue
                
                self.tree.insert('', 'end', values=(
                    '✓',  # 选择状态
                    len(self.tree.get_children())+1,  # 序号
                    ip,
                    row[remark_col] if remark_col else '',
                    '待检测',
                    '',
                    ''
                ))

            messagebox.showinfo("成功", f"已导入{len(df)}条记录")
        except Exception as e:
            messagebox.showerror("导入错误", str(e))

    def start_check(self):
        """开始检测"""
        if self.is_checking:
            return

        selected_items = [item for item in self.tree.get_children() 
                        if self.tree.item(item)['values'][0] == '✓']
        targets = selected_items if selected_items else self.tree.get_children()
        
        if not targets:
            messagebox.showwarning("警告", "没有可检测的IP地址")
            return

        self.is_checking = True
        self.update_controls(True)
        self.progress['value'] = 0
        self.total_batches = (len(targets) // self.max_workers) + 1
        
        # 创建新的线程池
        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers)
        threading.Thread(target=self.check_batch, args=(targets,), daemon=True).start()

    def check_batch(self, targets):
        """批量检测"""
        try:
            futures = {self.executor.submit(self.check_single, item): item for item in targets}
            
            for future in concurrent.futures.as_completed(futures):
                if not self.is_checking:
                    break
                
                item = futures[future]
                try:
                    status, latency = future.result(timeout=5)  # 添加超时
                    self.root.after(0, self.update_item, item, status, latency)
                except Exception as e:
                    self.root.after(0, self.update_item, item, f"错误: {str(e)}", None)
        finally:
            self.executor.shutdown(wait=False)
            self.executor = None
            self.is_checking = False
            self.root.after(0, self.update_controls, False)
            self.root.after(0, self.play_sound, 'complete')

    def check_single(self, item):
        """检测单个IP"""
        values = self.tree.item(item)['values']
        ip = values[2]
        
        try:
            latency = ping3.ping(ip, timeout=2, unit='ms')
            if latency is not None and latency > 0:
                return ("在线", latency)
            return ("离线", None)
        except Exception as e:
            return (f"错误: {str(e)}", None)

    def update_item(self, item, status, latency):
        """更新表格项"""
        values = list(self.tree.item(item)['values'])
        prev_status = values[4]
        
        values[4] = status
        values[5] = f"{latency:.2f}ms" if latency else ""
        values[6] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        # 更新颜色
        color = 'black'
        if '在线' in status:
            color = 'green'
            if '离线' in prev_status:
                self.play_sound('online')
        elif '离线' in status:
            color = 'red'
            if '在线' in prev_status:
                self.play_sound('offline')
        elif '错误' in status:
            color = 'orange'

        self.tree.item(item, values=values, tags=(color,))
        self.tree.tag_configure(color, foreground=color)
        
        # 更新进度
        self.progress['value'] += 100 / len(self.tree.get_children())
        self.root.update()

    def export_result(self):
        """导出结果"""
        try:
            file_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                filetypes=[("Excel文件", "*.xlsx")]
            )
            if not file_path:
                return

            data = []
            for item in self.tree.get_children():
                values = self.tree.item(item)['values']
                data.append({
                    '序号': values[1],
                    'IP地址': values[2],
                    '备注': values[3],
                    '状态': values[4],
                    '延迟': values[5],
                    '检测时间': values[6]
                })

            df = pd.DataFrame(data)
            df.to_excel(file_path, index=False)
            messagebox.showinfo("成功", "结果已导出")
        except Exception as e:
            messagebox.showerror("导出错误", str(e))

    # 其他辅助方法...
    def is_valid_ip(self, ip):
        """验证IP地址有效性"""
        try:
            ipaddress.ip_address(ip)
            return True
        except ValueError:
            return False

    def handle_click(self, event):
        """处理表格点击事件"""
        region = self.tree.identify_region(event.x, event.y)
        if region == "cell":
            column = self.tree.identify_column(event.x)
            item = self.tree.identify_row(event.y)
            
            if column == '#0':  # 选择列
                values = list(self.tree.item(item)['values'])
                values[0] = '✓' if values[0] != '✓' else ''
                self.tree.item(item, values=values)

    def create_tray_icon(self):
        """创建系统托盘图标"""
        image = Image.new('RGB', (64, 64), 'white')
        self.tray_icon = pystray.Icon(
            "network_checker",
            image,
            "网络检测工具",
            menu=pystray.Menu(
                pystray.MenuItem("显示主界面", self.show_window),
                pystray.MenuItem("退出", self.quit_app)
            )
        )
        threading.Thread(target=self.tray_icon.run, daemon=True).start()

    def on_closing(self):
        """处理窗口关闭事件"""
        self.save_settings()
        if self.is_checking:
            if messagebox.askyesno("确认", "检测正在进行中，确定要最小化到托盘吗？"):
                self.root.withdraw()
            return
        self.root.withdraw()

    def show_window(self):
        """显示主窗口"""
        self.root.deiconify()
        self.root.attributes('-topmost', 1)
        self.root.attributes('-topmost', 0)

    def quit_app(self):
        """退出程序"""
        self.stop_check()  # 确保停止所有检测
        if self.tray_icon:
            self.tray_icon.stop()
        self.root.destroy()

    def stop_check(self):
        """停止检测"""
        self.is_checking = False
        if self.executor:
            self.executor.shutdown(wait=False)
            self.executor = None
        self.update_controls(False)
        self.status_label.config(text="检测已停止")
    
    def update_controls(self, is_checking):
        """更新控件状态"""
        if (is_checking):
            self.load_btn.config(state=tk.DISABLED)
            self.check_btn.config(state=tk.DISABLED)
            self.stop_btn.config(state=tk.NORMAL)
            self.export_btn.config(state=tk.DISABLED)
            self.status_label.config(text="正在检测...")
        else:
            self.load_btn.config(state=tk.NORMAL)
            self.check_btn.config(state=tk.NORMAL)
            self.stop_btn.config(state=tk.DISABLED)
            self.export_btn.config(state=tk.NORMAL)
            self.status_label.config(text="就绪")

    def show_context_menu(self, event):
        """显示右键菜单"""
        try:
            self.tree.selection_set(self.tree.identify_row(event.y))
            self.context_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.context_menu.grab_release()
    
    def recheck_selected(self):
        """重新检测选中项"""
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择要重新检测的项目")
            return
        
        for item in selected:
            values = list(self.tree.item(item)['values'])
            values[4] = '待检测'
            values[5] = ''
            values[6] = ''
            self.tree.item(item, values=values)
        
        self.start_check()
    
    def toggle_selection(self):
        """切换选中项的选择状态"""
        for item in self.tree.selection():
            values = list(self.tree.item(item)['values'])
            values[0] = '✓' if values[0] != '✓' else ''
            self.tree.item(item, values=values)
    
    def delete_selected(self):
        """删除选中项"""
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择要删除的项目")
            return
        
        if messagebox.askyesno("确认", "确定要删除选中的项目吗？"):
            for item in selected:
                self.tree.delete(item)
    
    def clear_treeview(self):
        """清空表格"""
        if messagebox.askyesno("确认", "确定要清空所有内容吗？"):
            for item in self.tree.get_children():
                self.tree.delete(item)
    
    def add_ip_range(self):
        """添加IP地址段"""
        ip_text = self.ip_entry.get().strip()
        if not ip_text:
            messagebox.showwarning("警告", "请输入IP地址")
            return
        
        try:
            # 处理IP地址段 (例如: 192.168.1.1-10)
            if '-' in ip_text:
                start_ip, end = ip_text.split('-')
                base_ip = start_ip.rsplit('.', 1)[0]
                start = int(start_ip.split('.')[-1])
                end = int(end)
                
                for i in range(start, end + 1):
                    ip = f"{base_ip}.{i}"
                    if self.is_valid_ip(ip):
                        self.tree.insert('', 'end', values=(
                            '✓',
                            len(self.tree.get_children()) + 1,
                            ip,
                            '',
                            '待检测',
                            '',
                            ''
                        ))
            else:
                # 单个IP地址
                if self.is_valid_ip(ip_text):
                    self.tree.insert('', 'end', values=(
                        '✓',
                        len(self.tree.get_children()) + 1,
                        ip_text,
                        '',
                        '待检测',
                        '',
                        ''
                    ))
            
            self.ip_entry.delete(0, tk.END)
            
        except Exception as e:
            messagebox.showerror("错误", f"添加IP地址失败: {str(e)}")

    def sort_column(self, col, reverse=False):
        """排序表格列"""
        l = [(self.tree.set(k, col), k) for k in self.tree.get_children('')]
        try:
            # 尝试数字排序
            l.sort(key=lambda t: float(t[0].replace('ms', '')), reverse=reverse)
        except ValueError:
            # 按字符串排序
            l.sort(reverse=reverse)

        for index, (val, k) in enumerate(l):
            self.tree.move(k, '', index)
            # 更新ID列
            values = list(self.tree.item(k)['values'])
            values[1] = index + 1
            self.tree.item(k, values=values)

        # 切换排序方向
        self.tree.heading(col, command=lambda: self.sort_column(col, not reverse))

if __name__ == "__main__":
    app = NetworkCheckerApp()
    app.root.mainloop()